package org.andlisp.android;

import java.io.*;
import java.net.*;
import java.util.*;
import android.app.*;
import android.content.*;
import android.content.res.*;
import android.os.*;
import android.util.*;
import android.preference.*;
import jp.tyas.libs.lisp.Toplevel;
import jp.tyas.libs.lisp.Builtin;

public class LispService extends Service implements Runnable
{
  final static String LOG_TAG = "LispService";

  public final static int START_SERVER = Binder.FIRST_CALL_TRANSACTION + 1;
  public final static int STOP_SERVER  = Binder.FIRST_CALL_TRANSACTION + 2;
  public final static int OPEN_LISP    = Binder.FIRST_CALL_TRANSACTION + 3;
  public final static int CLOSE_LISP   = Binder.FIRST_CALL_TRANSACTION + 4;
  public final static int SCRIPT       = Binder.FIRST_CALL_TRANSACTION + 5;
  public final static int SEXP         = Binder.FIRST_CALL_TRANSACTION + 6;
  public final static int SERVER_STATUS= Binder.FIRST_CALL_TRANSACTION + 7;

  private int mPort;
  private int maxDesc = 0;
  private ServerSocket server = null;
  private Thread mainTask = null;
  private Hashtable tasks = new Hashtable();
  private Hashtable lisps = new Hashtable();

  class LispTask extends Thread {
    Reader in;
    PrintStream out;
    Toplevel lisp;
    LispTask(Reader in, PrintStream out) {
      this.in = in;
      this.out = out;
    }
    public void run() {
      lisp = new Toplevel (in, out);
      doRunScript(in, out, lisp);
    }
  }

  public String doRunScript(Reader in, PrintStream out, Toplevel lisp) {
    Object sexp, last = null;

    try {
      while (!Thread.interrupted()) {
        if (out != null)
          out.print(SafePreferenceManager.getString(this, "PromptString", "# "));
      
        sexp = Builtin.read(in, lisp);
        sexp = last = Builtin.eval(sexp, lisp);

        if (out != null)
          out.println(Builtin.getSign(sexp));
      }
    } catch (Throwable thr) {
      if ("EOF".equals(thr.getMessage()))
        return Builtin.getSign(last);
      if (out != null)
        thr.printStackTrace(out);
      return "LispService ERROR: " + thr.getMessage();
    }
    return "LispService INTERRUPTED";
  }

  public void run() {
    try {
      server = new ServerSocket(mPort);
    }      
    catch (Throwable t) {
      Log.i(LOG_TAG, "couldn't start sarver", t);
      server = null;
      return;
    }
    try {
      while (!Thread.interrupted()) {
        Socket sock;
        try {
          sock = server.accept();
        } catch (SocketException e) {
          break;
        }
        LispTask task = new LispTask
          (new InputStreamReader(sock.getInputStream()), new PrintStream(sock.getOutputStream()));
        tasks.put(Integer.valueOf(maxDesc++), task);
        task.start();
      }
      server.close();
    }
    catch (IOException e) {
      Log.e(LOG_TAG, "server task failure", e);
    }
    server = null;
    
    Enumeration values = tasks.elements();
    while (values.hasMoreElements())
      ((LispTask)values.nextElement()).interrupt();
    mainTask = null;

    Log.i(LOG_TAG, "exit accept task");
  }

  public int onStartCommand(Intent intent, int flasgs, int startId) {
    onStart(intent, startId);
    return START_STICKY;
  }

  public void onStart(Intent intent, int startId) {
  }

  private void stopServer() {
    if (mainTask != null) {
      mainTask.interrupt();
      try {
        if (server != null) {
          server.close();
        }
      } catch (IOException e) {}
      try {
        mainTask.join();
      } catch (InterruptedException e) {
        mainTask = null;
        server = null;
      }
    }
  }

  public void onDestroy() {
    Log.i(LOG_TAG, "onDestroy()");
    stopServer();
  }

  public IBinder onBind(Intent intent) {
    return new Binder() {
      protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
        Toplevel lisp;
        switch (code) {

        case START_SERVER:
          if (server != null || mainTask != null) {
            Log.d(LOG_TAG, server!=null ? "server != null" : "mainTask != null");
            return false;
          }
          startService(new Intent(LispService.this, LispService.class));
          mPort = data.readInt();
          mainTask = new Thread(LispService.this);
          mainTask.start();
          break;

        case STOP_SERVER:
          stopServer();
          stopSelf();
          break;

        case OPEN_LISP:
          lisp = new Toplevel(null, null);
          lisps.put(Integer.valueOf(maxDesc), lisp);
          reply.writeInt(maxDesc++);
          break;

        case CLOSE_LISP:
          lisps.remove(lisps.get(Integer.valueOf(data.readInt())));
          break;

        case SCRIPT:
        case SEXP:
          int desc = data.readInt();
          String str = data.readString();
          Reader reader;
          try {
            reader = code == SCRIPT ? new FileReader(str): new StringReader(str);
          } catch (FileNotFoundException e) {
            Log.e(LOG_TAG, "runSexp failure", e);
            return false;
          }
          lisp = desc == -1 ? new Toplevel(null, null): (Toplevel)lisps.get(Integer.valueOf(desc));
          reply.writeString(doRunScript(reader, null, lisp));
          break;

        case SERVER_STATUS:
          if (server == null && mainTask == null) {
            Log.i(LOG_TAG, "server status 0");
            reply.writeInt(0);
          } else if (server == null) {
            Log.i(LOG_TAG, "server status 1");
            reply.writeInt(1);
          } else {
            Log.i(LOG_TAG, "server status 2");
            reply.writeInt(2);
            reply.writeInt(server.getLocalPort());
          }
          break;
        }
        return true;
      }
    };
  }
  
  /*-----------------------------------------------------------------------------*
   * static utilities
   *-----------------------------------------------------------------------------*/

  static abstract class SCBase implements ServiceConnection {
    private Context mCtx;
    SCBase(Context ctx) {
      mCtx = ctx;
    }
    public void onServiceConnected(ComponentName name, IBinder ibinder) {
      onServiceConnectedImpl(ibinder);
      mCtx.unbindService(this);
    }
    public void onServiceDisconnected(ComponentName name) {}
    abstract void onServiceConnectedImpl(IBinder ibinder);
  }
  
  public static boolean connectLispService(Context context, ServiceConnection conn) {
    Intent intent = new Intent(context, LispService.class);
    return context.bindService(intent, conn, Context.BIND_AUTO_CREATE);
  }

  public static interface SCWithLispListener {
    void onSCWithLisp(IBinder binder, int desc);
  }

  static class SCWithLisp extends SCBase {
    SCWithLispListener mLis;
    SCWithLisp(Context ctx, SCWithLispListener l) {
      super(ctx);
      mLis = l;
    }
    void onServiceConnectedImpl(IBinder binder) {
      int desc = openLisp(binder);
      if (mLis != null)
        mLis.onSCWithLisp(binder, desc);
      closeLisp(binder, desc);
    }
  }

//   Example:
//   class LispExample implements SCWithLispListener {
    
//     LispExample(Context ctx) {
//       invokeLisp(ctx, this);
//     }

//     void onSCWithLisp(IBinder binder, int desc) {
//       runSexp(binder, "(setq *a* 1)", desc);
//       runSexp(binder, "(setq *b* 2)", desc);
//       println(runScript(binder, "/sdcard/download/myscript.l", desc));
//     }

//   }

  public static boolean invokeLisp(Context ctx, SCWithLispListener l) {
    return connectLispService(ctx, new SCWithLisp(ctx, l));
  }

  public static int openLisp(IBinder ibinder) {
    Parcel reply = Parcel.obtain();
    try {
      if (!ibinder.transact(OPEN_LISP, null, reply, 0))
        return -1;
    } catch (RemoteException e) {
      Log.e(LOG_TAG, "openLisp failure", e);
      return -1;
    }
    return reply.readInt();
  }

  public static void closeLisp(IBinder ibinder, int desc) {
    Parcel data = Parcel.obtain();
    data.writeInt(desc);
    try {
      ibinder.transact(CLOSE_LISP, data, null, 0);
    } catch (RemoteException e) {
      Log.e(LOG_TAG, "closeLisp failure", e);
    }
  }

  public static boolean startServer(IBinder ibinder, int port) {
    if (ibinder != null) {
      Parcel data = Parcel.obtain();
      data.writeInt(port);
      try {
        return ibinder.transact(START_SERVER, data, null, 0);
      } catch (RemoteException e) {
        Log.e(LOG_TAG, "startServer failure", e);
        return false;
      }
    }
    Log.e(LOG_TAG, "startServer: ibinder == null");
    return false;
  }

  public static interface SCServerStartListener {
    void onSCServerStart(IBinder binder, boolean success);
  }

  static class SCServerStart extends SCBase {
    private int mPort;
    private SCServerStartListener mListener;
    SCServerStart(Context context, int port, SCServerStartListener l) {
      super(context);
      mPort = port;
      mListener = l;
    }
    void onServiceConnectedImpl(IBinder ibinder) {
      boolean b = startServer(ibinder, mPort);
      if (mListener != null)
        mListener.onSCServerStart(ibinder, b);
    }
  }
    
  public static boolean startServerAsync(Context context, int port, SCServerStartListener l) {
    return connectLispService(context, new SCServerStart(context, port, l));
  }

  public static boolean startServerAsync(Context context, int port) {
    return startServerAsync(context, port, null);
  }

  public static void stopServer(IBinder ibinder) {
    try {
      if (ibinder != null)
        ibinder.transact(STOP_SERVER, null, null, 0);
    } catch (RemoteException e) {
      Log.e(LOG_TAG, "stopServer failure", e);
    }
  }

  public static interface SCServerStopListener {
    void onSCServerStop(IBinder binder);
  }

  static class SCServerStop extends SCBase {
    SCServerStopListener mListener;
    SCServerStop(Context context, SCServerStopListener l) {
      super(context);
      mListener = l;
    }
    void onServiceConnectedImpl(IBinder ibinder) {
      stopServer(ibinder);
      if (mListener != null)
        mListener.onSCServerStop(ibinder);
    }
  }

  public static boolean stopServerAsync(Context context, SCServerStopListener l) {
    return connectLispService(context, new SCServerStop(context, l));
  }

  public static String runScript(IBinder ibinder, String filename, int desc) {
    Parcel reply = Parcel.obtain();
    Parcel data = Parcel.obtain();
    data.writeInt(desc);
    data.writeString(filename);
    try {
      if (!ibinder.transact(SCRIPT, data, reply, 0))
        return null;
    } catch (RemoteException e) {
      Log.e(LOG_TAG, "runScript failure", e);
      return null;
    }
    return reply.readString();
  }

  public static interface SCRunScriptListener {
    void onSCRunScript(String result);
  }

  static class SCRunScript extends SCBase {
    SCRunScriptListener mListener;
    String mFile;
    int mDesc;
    SCRunScript(Context context, String filename, int desc, SCRunScriptListener l) {
      super(context);
      mFile = filename;
      mDesc = desc;
      mListener = l;
    }
    void onServiceConnectedImpl(IBinder ibinder) {
      String r = runScript(ibinder, mFile, mDesc);
      if (mListener != null)
        mListener.onSCRunScript(r);
    }
  }

  public static boolean runScriptAsync(Context context, String filename, int desc, SCRunScriptListener l) {
    return connectLispService(context, new SCRunScript(context, filename, desc, l));
  }

  public static boolean runScriptAsync(Context context, String filename, int desc) {
    return runScriptAsync(context, filename, desc, null);
  }

  public static String runSexp(IBinder ibinder, String sexp, int desc) {
    Parcel reply = Parcel.obtain();
    Parcel data = Parcel.obtain();
    data.writeInt(desc);
    data.writeString(sexp);
    try {
      if (!ibinder.transact(SEXP, data, reply, 0))
        return null;
    } catch (RemoteException e) {
      Log.e(LOG_TAG, "runSexp failure", e);
      return null;
    }
    return reply.readString();
  }

  public static interface SCRunSexpListener {
    void onSCRunSexp(String result);
  }

  static class SCRunSexp extends SCBase {
    String mSexp;
    int mDesc;
    SCRunSexpListener mListener;
    SCRunSexp(Context context, String sexp, int desc, SCRunSexpListener l) {
      super(context);
      mSexp = sexp;
      mDesc = desc;
      mListener = l;
    }
    void onServiceConnectedImpl(IBinder ibinder) {
      String r = runSexp(ibinder, mSexp, mDesc);
      if (mListener != null)
        mListener.onSCRunSexp(r);
    }
  }

  public static boolean runSexpAsync(Context context, String sexp, int desc, SCRunSexpListener l) {
    return connectLispService(context, new SCRunSexp(context, sexp, desc, l));
  }

  public static boolean runSexpAsync(Context context, String sexp, int desc) {
    return runSexpAsync(context, sexp, desc, null);
  }

  public static boolean getServerStatus(IBinder ibinder) {
    try {
      if (ibinder != null) {
        Parcel reply = Parcel.obtain();
        if (!ibinder.transact(SERVER_STATUS, null, reply, 0)) {
          Log.e(LOG_TAG, "getServerStatus: IBinder.transact failure");
          return false;
        }
        return reply.readInt() != 0;
      }
      Log.e(LOG_TAG, "getServerStatus: ibinder == null");
    } catch (RemoteException e) {
      Log.e(LOG_TAG, "getServerStatus failure", e);
    }
    return false;
  }

  public static interface SCServerStatusListener {
    void onSCServerStatus(IBinder binder, boolean running);
  }

  static class SCServerStatus extends SCBase {
    SCServerStatusListener mListener;
    SCServerStatus(Context context, SCServerStatusListener l) {
      super(context);
      mListener = l;
    }
    void onServiceConnectedImpl(IBinder ibinder) {
      boolean r = getServerStatus(ibinder);
      if (mListener != null)
        mListener.onSCServerStatus(ibinder, r);
    }
  }

  public static boolean getServerStatusAsync(Context context, SCServerStatusListener l) {
    return connectLispService(context, new SCServerStatus(context, l));
  }
}
